#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "cmd.h"
#include "log.h"

typedef struct {
    const char *prog_name;
    /* 所有选项的配置信息 */
    cmd_opt_cfg *opt_cfgs;

    /* 用于getopt_long的选项配置，最后一个需要是NULL */
    struct option *long_opts;
    uint32_t opt_cnt; /* 选项个数 */
    uint32_t max_opt_num;
} cmd_opt_configs;

cmd_opt_configs *g_cmd_opt_configs;

static void __attribute__((constructor(120), used)) cmd_constructor(void)
{
    g_cmd_opt_configs = (cmd_opt_configs *)malloc(sizeof(cmd_opt_configs));
    if (NULL == g_cmd_opt_configs)
    {
        fprintf(stderr, "[CCMD] Malloc mem for configs error!\r\n");
        return;
    }

    memset(g_cmd_opt_configs, 0, sizeof(cmd_opt_configs));

    g_cmd_opt_configs->max_opt_num = DEFAULT_OPTION_NUM;
    g_cmd_opt_configs->opt_cfgs = (cmd_opt_cfg *)malloc(sizeof(cmd_opt_cfg) * (DEFAULT_OPTION_NUM + 1));
    if (NULL == g_cmd_opt_configs->opt_cfgs)
    {
        fprintf(stderr, "[CCMD] Malloc mem for opt configs error!\r\n");
        free(g_cmd_opt_configs);
        g_cmd_opt_configs = NULL;
        exit(1);
    }

    g_cmd_opt_configs->long_opts = (struct option *)malloc(sizeof(struct option) * (DEFAULT_OPTION_NUM + 1));
    if (NULL == g_cmd_opt_configs->long_opts)
    {
        fprintf(stderr, "[CCMD] Malloc mem for opt configs error!\r\n");
        free(g_cmd_opt_configs->opt_cfgs);
        free(g_cmd_opt_configs);
        g_cmd_opt_configs = NULL;
        exit(1);
    } 
}

static void __attribute__((destructor(120), used)) cmd_destroctor(void)
{
    CCMD_LOG_DBG("clean command space.\r\n");
    if (NULL == g_cmd_opt_configs)
    {
        return;
    }

    if (g_cmd_opt_configs->long_opts)
    {
        free(g_cmd_opt_configs->long_opts);
    }

    if (g_cmd_opt_configs->opt_cfgs)
    {
        free(g_cmd_opt_configs->opt_cfgs);
    }

    free(g_cmd_opt_configs);
}


/**
*@ Description: 用于判断未知选项程序是否继续执行
*
*@ return true：继续执行 false: 输出usage并退出
*/
__attribute__((weak)) bool __command_unknown_option_bypass(void) 
{
    return false;
}


/**
*@ Description: 查找指定选项名称的配置
*@ name: [in] 选项名
*@ return 非NULL: 选项配置指针 NULL:不存在改选项配置
*/
static cmd_opt_cfg *find_opt_cfg(char *name)
{
    int i = 0;
    cmd_opt_cfg *pconfig = NULL;

    for (i = 0; i < g_cmd_opt_configs->opt_cnt; i++)
    {
        if (0 == strcmp( g_cmd_opt_configs->opt_cfgs[i].name, name))
        {
            pconfig = & g_cmd_opt_configs->opt_cfgs[i];
        }
    }

    return pconfig;
}

/**
*@ Description: 增加一个选项配置
*@ cfg: [in] 选项配置
*@ return 0：成功 非0：失败
*/
int add_cmd_opt(cmd_opt_cfg *cfg, struct option *opt_cfg)
{
    if (NULL == cfg || NULL == opt_cfg)
    {
        return -1;
    }

    cmd_opt_cfg *pconfig =  find_opt_cfg((char*)cfg->name);
    if (pconfig)
    {
        *pconfig = *cfg;
        g_cmd_opt_configs->long_opts[pconfig- g_cmd_opt_configs->opt_cfgs] = *opt_cfg;
    }
    else
    {
        if ( g_cmd_opt_configs->opt_cnt < g_cmd_opt_configs->max_opt_num)
        {
            g_cmd_opt_configs->opt_cfgs[ g_cmd_opt_configs->opt_cnt] = *cfg;
            g_cmd_opt_configs->long_opts[ g_cmd_opt_configs->opt_cnt] = *opt_cfg;
            g_cmd_opt_configs->opt_cnt++;
        }
        else
        {
            CCMD_LOG_ERR("Count of option reach max limit %d\r\n", g_cmd_opt_configs->max_opt_num);
            return -1;
        }
    }

    CCMD_LOG_DBG("Add option %s succeed\r\n", cfg->name);
    
    return 0;    
}

/**
*@ Description: 输出帮助的函数
*@ progname: [in] 程序名
*@ return void
*/
void usage(void)
{
     printf("Usage\n"
         " %s [options] ...\n"
         "Options:\r\n",
        g_cmd_opt_configs->prog_name);


    uint32_t i = 0;
    for (; i < g_cmd_opt_configs->opt_cnt; i++)
    {
        if ( g_cmd_opt_configs->opt_cfgs[i].help) 
        {
            printf("%s", g_cmd_opt_configs->opt_cfgs[i].help);
        }
        else if ( g_cmd_opt_configs->opt_cfgs[i].help_fn)
        {
            g_cmd_opt_configs->opt_cfgs[i].help_fn( g_cmd_opt_configs->opt_cfgs[i].name);
        }
    }
}

/**
*@ Description: 选项解析
*@ opt_idx: [in] 选项在g_command_long_options中对应的索引
*@ options: [in, out] 选项解析结果
*@ return void
*/
static int cmd_parse_opt_with_idx(int opt_idx, void *cmd_results)
{
    if ( g_cmd_opt_configs->opt_cfgs[opt_idx].parser_fn)
    {
        CCMD_LOG_DBG("parse option %s\r\n", g_cmd_opt_configs->opt_cfgs[opt_idx].name);
        return g_cmd_opt_configs->opt_cfgs[opt_idx].parser_fn(cmd_results, (char *) g_cmd_opt_configs->opt_cfgs[opt_idx].name, optarg);
    }

    
    return 0;
}


/**
*@ Description: 解析命令行选项
*@ options: [in, out] 选项解析结果
*@ argc: [in] 选项个数
*@ argv: 选项数组
*@ return void
*/
int cmd_parse(void *results, const int argc, const char **argv)
{
    int opt, retval, opt_idx;
    int ret = 0;

    g_cmd_opt_configs->prog_name = argv[0];
    
    while ((opt = getopt_long(argc, (char * const*)argv, "", g_cmd_opt_configs->long_opts, &opt_idx)) != EOF) 
    {
        switch (opt) 
        {
            case 0:
                retval = cmd_parse_opt_with_idx(opt_idx, results);
                if (retval != 0)
                {
                    usage();
                    return retval;
                }
                break;

            default: // hit unknown option
                if (!__command_unknown_option_bypass())
                {
                    usage();
                    exit(EXIT_FAILURE);
                }
                break;
        }
    }

    return 0;
}


